package net.sf.midollo.net.cgitunnel.server;

import net.sf.midollo.io.IOStream;
import net.sf.midollo.io.log.LogInputStream;
import net.sf.midollo.io.text.Formatter;
import net.sf.midollo.io.text.Parser;
import net.sf.midollo.net.http.text.HttpFormatter;
import net.sf.midollo.net.http.text.HttpParser;
import net.sf.midollo.net.httptunnel.HttpTunnelConstants;
import net.sf.midollo.net.httptunnel.server.HttpTunnelServer;
import net.sf.midollo.net.httptunnel.server.HttpTunnelServerSession;
import net.sf.midollo.net.protocol.Protocol;
import net.sf.midollo.net.protocol.ProtocolFactory;
import net.sf.midollo.net.server.AsyncAcceptHandler;
import net.sf.midollo.net.server.InetServer;
import net.sf.midollo.utils.Utils;

import java.io.*;

public class Main {

	public static void main(String[] args) {
		try {
			String strLocalPort = null;
			for (int idx = 0; idx < args.length; ++idx) {
				if (args[idx].compareTo("-p") == 0) {
					if (++idx < args.length) {
						strLocalPort = args[idx];
					}
				}
			}

			int localPort = Utils.parseInt(strLocalPort, 0);

			CGITunnelServerFactory protocolFactory = new CGITunnelServerFactory();

			HttpTunnelServer server = new HttpTunnelServer(protocolFactory);
			HttpTunnelRawServerHelper helper = new HttpTunnelRawServerHelper(server);

			HttpRawServerFactory httpRawServerFactory = new HttpRawServerFactory(helper);
			AsyncAcceptHandler acceptHandler = new AsyncAcceptHandler(httpRawServerFactory);
			InetServer inetServer = new InetServer(localPort, acceptHandler);
			inetServer.run();

		} catch (Throwable ex) {
			ex.printStackTrace();
		}
	}

	protected static class HttpRawServerFactory implements ProtocolFactory {

		protected final HttpTunnelRawServerHelper helper;

		public HttpRawServerFactory(HttpTunnelRawServerHelper helper) {
			this.helper = helper;
		}

		public Protocol connect(IOStream ioRequest) throws IOException {
			return new HttpRawServer(ioRequest, this.helper);
		}
	}

	protected static class HttpRawServer implements Protocol {

		protected final IOStream ioRequest;
		protected final HttpTunnelRawServerHelper helper;
		protected IOException error;

		public HttpRawServer(IOStream ioRequest, HttpTunnelRawServerHelper helper) throws IOException {
			this.ioRequest = ioRequest;
			this.helper = helper;
		}

		public IOException getError() {
			return this.error;
		}

		public void disconnect() throws IOException {
		}

		public void run() {
			try {
				this.error = null;
				runProtocol();
			} catch (IOException e) {
				this.error = e;
			}
		}

		public void runProtocol() throws IOException {
			InputStream is = this.ioRequest.getInput();
			//is = new LogInputStream(is, System.out);
			OutputStream os = this.ioRequest.getOutput();
			//os = new LogOutputStream(os, System.out);
			while (this.helper.dispatch(is, os))
				;
		}
	}

	protected static class HttpTunnelRawServerHelper implements HttpTunnelConstants {

		protected static int counter = 0;

		protected final HttpTunnelServer server;

		public HttpTunnelRawServerHelper(HttpTunnelServer server) {
			this.server = server;
		}

		public boolean dispatch(InputStream is, OutputStream os) throws IOException {
			Parser parser = new Parser(is);
			HttpParser httpParser = new HttpParser(parser);
			int idxContentLength = httpParser.registerHeaderName("Content-Length");
			int idxConnection = httpParser.registerHeaderName("Connection");
			int idxSessionId = httpParser.registerHeaderName(SESSION_HEADER_NAME);
			httpParser.parseRequest();
			Formatter formatter = new Formatter(os);
			HttpFormatter httpFormatter = new HttpFormatter(formatter);
			String sessionId = httpParser.getHeaderValue(idxSessionId);
			HttpTunnelServerSession session;
			if (sessionId == null) {
				session = this.server.createSession();
				httpFormatter.formatResponseLine(1, 0, 200, "OK");
				httpFormatter.formatHeaderLine("Content-Type", "text/plain");
				httpFormatter.formatHeaderLine(SESSION_HEADER_NAME, session.getSessionId());
				formatter.formatCRLF();
				os.flush();
				session.writeLoop(os);
				return false;
			} else {
				int contentLength = -1;
				try {
					contentLength = (int)httpParser.getHeaderValueLong(idxContentLength);
				} catch (Exception e) {
				}
				if (contentLength < 0)
					throw new IOException("Content-Length required");
				String connection = httpParser.getHeaderValue(idxConnection);
				boolean keepAlive;
				if (connection != null)
					keepAlive = (connection.compareToIgnoreCase("keep-alive") == 0);
				else
					keepAlive = false;
				session = this.server.getSession(sessionId);
				try {
					String strCounter = Integer.toString(counter++);
					while (strCounter.length() < 6)
						strCounter = '0' + strCounter;
					File file = new File("logs/x" + strCounter + ".log");
					File parent = file.getParentFile();
					if (parent != null)
						parent.mkdirs();
					FileOutputStream fos = new FileOutputStream(file);
					LogInputStream is_logged = new LogInputStream(is, fos);
					session.readLoop(is_logged, contentLength);
				} catch (IOException e) {
					String msg = e.toString();
					httpFormatter.formatResponseLine(1, 0, 500, "Server error");
					httpFormatter.formatHeaderContentLength(msg.length());
					formatter.formatCRLF();
					os.write(msg.getBytes());
					os.flush();
					return false;
				}
				httpFormatter.formatResponseLine(1, 0, 204, "OK");
				if (keepAlive)
					httpFormatter.formatHeaderLine("Connection", "keep-alive");
				formatter.formatCRLF();
				os.flush();
				return keepAlive;
			}
		}
	}
}
